<template>
  <div class="container" ref="container">
    <!-- 3D -->
    <div id="model" ref="model"></div>
    <!-- 注释 -->
    <div class="line" ref="line">
      <span>{{ checkedModel && checkedModel.name }}</span>
      <img src="../assets/line.png" v-if="checkedModel" />
    </div>
  </div>
</template>

<script>
// 引入the three.js库
import * as THREE from "three";
// import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { CSS2DObject } from "three/addons/renderers/CSS2DRenderer.js";
import { CSS2DRenderer } from "three/addons/renderers/CSS2DRenderer.js";
// 引入后处理扩展库EffectComposer.js
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
// 引入渲染器通道RenderPass
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
// 引入OutlinePass通道
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";

export default {
  name: "HelloWorld",
  data() {
    return {
      scene: null, // 场景
      camera: null, // 相机
      renderer: null, // 渲染器
      controls: null, // 轨道控制器
      meshArr: [], // 模型数组，用于点击事件处理
      checkedModel: null, // 被点击的模型，用于显示标签内容
      css2Renderer: null, // 用于显示标签内容的渲染器
      css2DObject: null, // 用于显示标签内容的CSS2D对象，需要添加到场景中
      composer: null, // 后处理
      renderPass: null, // 渲染通道
      outlinePass: null, // 描边通道
    };
  },
  mounted() {
    this.init();
    this.initTag();
    this.onWindowResize();
  },
  watch: {
    checkedModel(newValue) {
      // 将被点击的区域突显出来
      // this.meshArr.map((m) => {
      //   m.material.color.set(0xffff00);
      //   if (m.name === newValue.name) {
      //     m.material.color.set(0xffffff);
      //   }
      // });
      // 呼吸灯效果凸显出被点击的模型
      console.log(222,this.outlinePass);
      
      this.outlinePass.selectedObjects = [newValue];
      // 将标签内容的中心点设置在被点击区域的中心点上
      const box = new THREE.Box3().setFromObject(newValue);
      const center = new THREE.Vector3();
      const modelCenter = box.getCenter(center);
      this.css2DObject.position.set(
        modelCenter.x,
        modelCenter.y,
        modelCenter.z
      );
    },
  },
  methods: {
    // 初始化场景、相机、渲染器
    init() {
      // 创建场景、相机、渲染器
      this.scene = new THREE.Scene();
      this.camera = new THREE.PerspectiveCamera(
        45,
        window.innerWidth / window.innerHeight,
        0.25,
        1000
      );

      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.setSize(window.innerWidth, window.innerHeight);

      // this.renderer.setAnimationLoop(this.render());
      this.$refs["model"].appendChild(this.renderer.domElement);

      //模型悬浮于背景上
      this.renderer.domElement.style.position = "absolute";

      // 设置背景颜色
      this.renderer.setClearColor(0x000000, 0.1);
      // 设备背景色透明
      // this.renderer.setClearAlpha(0.0);
      this.renderer.render(this.scene, this.camera);

      // 设置相机位置
      this.camera.position.set(10, 100, 0);
      this.scene.add(this.camera);

      //添加场景辅助线
      // const gridHelper = new THREE.GridHelper(100, 20, 0x000000, 0xf000000);
      // this.scene.add(gridHelper);

      this.addControls();
      this.initlight();
      this.loadModel();
      this.render();
      this.composerUpdate();
      // 创建模型点击事件
      this.renderer.domElement.addEventListener(
        "click",
        this.onClickRenderer,
        false
      );
    },
    // 光源
    initlight() {
      // 添加环境光
      const ambientLight = new THREE.AmbientLight(0xffffff, 5);
      this.scene.add(ambientLight);
      // 添加直射光源
      const positions = [
        { x: 1000, y: 1000, z: 1000 },
        { x: -1000, y: 1000, z: -1000 },
        { x: -3000, y: 1000, z: 0 },
        { x: 0, y: -1000, z: 0 },
      ];
      positions.forEach((pos) => {
        const light = new THREE.DirectionalLight(0xffffff, 5);
        light.position.set(pos.x, pos.y, pos.z);
        this.scene.add(light);
      });
    },
    // 加载模型
    loadModel() {
      //改模型被压缩过，对模型解压
      // const dracoLoader = new DRACOLoader();
      // dracoLoader.setDecoderPath("./draco/gltf/");
      const gltfLoader = new GLTFLoader();
      // gltfLoader.setDRACOLoader(dracoLoader);
      gltfLoader.load(
        "/model/fj.glb",
        (glb) => {
          console.log(glb);
          // 改变材质颜色
          glb.scene.traverse((node) => {
            if (node.isMesh && node.material) {
              //将mesh 数据添加到meshArr数组中
              this.meshArr.push(node);
              // 修改材质
              // node.material.color.set(0xffff00); // 颜色
              // node.material.metalness = 0.5; //金属度
              // node.material.roughness = 0.5; //表面粗糙度
            }
          });
          this.useModelSizeAPosion(glb.scene);
        },
        (progressEvent) => {
          const { loaded, total, lengthComputable } = progressEvent;
          //lengthComputable  true 可以获取到当前文件的总字节大小
          if (lengthComputable) {
            const progress = (loaded / total) * 100;
            console.log(progress + "% loaded");
          } else {
            console.log("已加载" + loaded + "字节");
          }
        }
      );
    },
    loadFbxModel() {
    },
    // 模型自适应
    useModelSizeAPosion(modelScene) {
      //模型自适应大小
      const box = new THREE.Box3().setFromObject(modelScene);
      const size = box.getSize(new THREE.Vector3());
      // 计算缩放比例
      const maxSize = Math.max(size.x, size.y, size.z);
      const scale = 65 / maxSize;
      modelScene.scale.set(scale, scale, scale);

      //模型居中
      const box3 = new THREE.Box3();
      box3.expandByObject(modelScene);
      const center = new THREE.Vector3(0, 0, 0);
      box3.getCenter(center);

      modelScene.position.x = modelScene.position.x - center.x;
      modelScene.position.y = modelScene.position.y - center.y;
      modelScene.position.z = modelScene.position.z - center.z;
      this.scene.add(modelScene);
    },
    // 添加轨道控制器
    addControls() {
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    },
    // 渲染器
    render() {
      requestAnimationFrame(this.render);
      this.controls.update();
      this.renderer.render(this.scene, this.camera);
      this.css2Renderer && this.css2Renderer.render(this.scene, this.camera);
      this.composer && this.composer.render();
    },
    onWindowResize() {
      // 监听画面变化，更新渲染画面
      window.addEventListener("resize", () => {
        // 更新摄像头
        this.camera.aspect = window.innerWidth / window.innerHeight;
        //   更新摄像机的投影矩阵
        this.camera.updateProjectionMatrix();
        //   更新渲染器
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        //   设置渲染器的像素比
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.css2Renderer.setSize(window.innerWidth, window.innerHeight);
      });
    },
    // 模型点击事件
    onClickRenderer(event) {
      const width = window.innerWidth;
      const height = window.innerHeight;
      // .offsetY、.offsetX以canvas画布左上角为坐标原点,单位px
      const px = event.offsetX;
      const py = event.offsetY;
      //屏幕坐标px、py转WebGL标准设备坐标x、y
      //width、height表示canvas画布宽高度
      const x = (px / width) * 2 - 1;
      const y = -(py / height) * 2 + 1;
      //创建一个射线投射器`Raycaster`
      const raycaster = new THREE.Raycaster();
      //.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray
      // 形象点说就是在点击位置创建一条射线，射线穿过的模型代表选中
      raycaster.setFromCamera(new THREE.Vector2(x, y), this.camera);
      //.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算
      // 未选中对象返回空数组[],选中一个对象，数组1个元素，选中两个对象，数组两个元素
      const intersects = raycaster.intersectObjects(this.meshArr);
      // intersects.length大于0说明，说明选中了模型
      if (intersects.length > 0) {
        this.checkedModel = intersects[0].object;
      }
    },
    // 注释内容
    initTag() {
      const line = this.$refs["line"];
      // HTML元素转化为threejs的CSS2模型对象
      this.css2DObject = new CSS2DObject(line);
      this.css2DObject.center.set(0, 1, 0);
      this.scene.add(this.css2DObject);
      // this.css2DObject.layers.set(0);
      // 创建一个CSS2渲染器CSS2DRenderer
      this.css2Renderer = new CSS2DRenderer();
      this.css2Renderer.setSize(window.innerWidth, window.innerHeight);
      this.css2Renderer.domElement.style.position = "absolute";
      this.css2Renderer.domElement.style.top = "0px";
      this.css2Renderer.domElement.style.pointerEvents = "none";
      this.$refs["container"].appendChild(this.css2Renderer.domElement);
    },
    // 呼吸灯效果
    composerUpdate() {
      // 创建后处理对象EffectComposer，WebGL渲染器作为参数
      this.composer = new EffectComposer(this.renderer);
      // 创建渲染通道RenderPass
      this.renderPass = new RenderPass(this.scene, this.camera);
      // 添加通道到后处理对象中
      this.composer.addPass(this.renderPass);
      // 创建OutlinePass通道
      const v2 = new THREE.Vector2(window.innerWidth, window.innerHeight);
      this.outlinePass = new OutlinePass(v2, this.scene, this.camera);
      //模型描边颜色，默认白色
      this.outlinePass.visibleEdgeColor.set(0xc0dcff);
      //高亮发光描边厚度
      this.outlinePass.edgeThickness = 4;
      //高亮描边发光强度
      this.outlinePass.edgeStrength = 6;
      //模型闪烁频率控制，默认0不闪烁
      this.outlinePass.pulsePeriod = 2;
      this.composer.addPass(this.outlinePass);
    },
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.container {
  height: 100%;
  width: 100%;
}
#model {
  height: 100%;
  width: 100%;
}
.line {
  width: 200px;
}
img {
  width: 100%;
}
span {
  position: relative;
  left: 62px;
  top: -5px;
  font-size: 18px;
  color: #fff;
}
</style>
